/*=======================================================

    HCView V1.0
    文档QRCodeItem(二维码)对象实现单元
    作者：荆通(18114532@qq.com)
    QQ群：649023932

=======================================================*/

import { THCResizeRectItem } from "./HCRectItem.js";
import { THCStyle } from "./HCStyle.js";
import { HC } from "./HCCommon.js";
import { TBitmap } from "../hcl/Bitmap.js";
import { system, TObject, TUtf8Encoding, TList, TIntegerArray, TByteArray, TObjectArray } from "../hcl/System.js";
import { TColor } from "../hcl/Graphics.js";

export var TQRCodeEncoding = {
    Auto: 0,
    Numeric: 1,
    Alphanumeric: 2,
    ISO88591: 3,
    UTF8NoBOM: 4,
    UTF8BOM: 5
}

export var TQRMode = {
    Terminator: 0,
    /**
     * numeric encoding
    */
    Numeric: 1,
    /**
     * alpha-numeric encoding
     */
    Alphanumeric: 2,
    /**
     * structured append
     */
    StructuredAppend: 3,
    /**
     * byte mode encoding
     */
    Byte: 4,
    /**
     * ECI segment
     */
    ECI: 5,
    /**
     * Kanji mode
     */
    Kanji: 6,
    /**
     * FNC1 char, first position
     */
    FNC1FirstPosition: 7,
    /**
     * FNC1 char, second position
     */
    FNC1SecondPosition: 8,
    /**
     * Hanzi mode
     */
    Hanzi: 9
}

class TErrorCorrectionLevel extends TObject {
    constructor() {
        super();
        this.Bits = 0;
        this.Ordinal = 0;
    }

    Assign(source) {
        this.Bits = source.Bits;
    }
}

class TBitArray extends TObject {
    constructor(size = 0) {
        super();
        this.Size = size;
        if (size > 0)
            this.Bits = new TIntegerArray((size + 31) >> 5);
        else
            this.Bits = new TIntegerArray(1);
    }

    EnsureCapacity(size) {
        if (size > (this.Bits.length << 5)) {
            let vLen = this.Bits.length;
            this.Bits.length = size;
            for (let i = vLen; i < this.Bits.length; i++)
                this.Bits[i] = 0;
        }
    }

    GetSizeInBytes() {
        return (this.Size + 7) >> 3;
    }

    GetSize() {
        return this.Size;
    }

    Get(i) {
        return (this.Bits[i >> 5] & (1 << (i & 0x1F))) != 0;
    }

    SetBit(i) {
        this.Bits[i >> 5] |= 1 << (i & 0x1F);
    }

    AppendBit(bit) {
        this.EnsureCapacity(this.Size + 1);
        if (bit)
            this.Bits[this.Size >> 5] |= 1 << (this.Size & 0x1F);

        this.Size++;
    }

    AppendBits(value, numBits) {
        this.EnsureCapacity(this.Size + numBits);
        for (let i = numBits; i > 0; i--)
            this.AppendBit(((value >> (i - 1)) & 0x01) == 1);
    }

    AppendBitArray(newBitArray) {
        let vOtherSize = newBitArray.GetSize();
        this.EnsureCapacity(this.Size + vOtherSize);
        for (let i = 0; i < vOtherSize; i++)
            this.AppendBit(newBitArray.Get(i));
    }

    ToBytes(bitOffset, source, offset, numBytes) {
        let vByte;
        for (let i = 0; i < numBytes; i++) {
            vByte = 0;
            for (let j = 0; j < 8; j++) {
                if (this.Get(bitOffset))
                    vByte |= (1 << (7 - j));

                bitOffset++;
            }

            source[offset + i] = vByte;
        }
    }

    XorOperation(other) {
        if (this.Bits.length == other.Bits.length) {
            for (let i = 0; i < this.Bits.length; i++)
                this.Bits[i] ^= other.Bits[i];
        }
    }
}

let ALPHANUMERIC_TABLE = [
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x00-0x0f
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x10-0x1f
    36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,  // 0x20-0x2f
    0,   1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,  // 0x30-0x3f
    -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  // 0x40-0x4f
    25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1   // 0x50-0x5f
];

let ModeCharacterCountBits = [
    [0, 0, 0], [10, 12, 14], [9, 11, 13], [0, 0, 0], [8, 16, 16],
    [0, 0, 0], [8, 10, 12], [0, 0, 0], [0, 0, 0], [8, 10, 12]
];

let ModeBits = [0, 1, 2, 3, 4, 7, 8, 5, 9, 13];

let TYPE_INFO_COORDINATES = [
    [8, 0], [8, 1], [8, 2],
    [8, 3], [8, 4], [8, 5],
    [8, 7], [8, 8], [7, 8],
    [5, 8], [4, 8], [3, 8],
    [2, 8], [1, 8], [0, 8]
];

let NUM_MASK_PATTERNS = 8,
    VERSION_INFO_POLY = 0x1F25,
    TYPE_INFO_POLY = 0x537,
    TYPE_INFO_MASK_PATTERN = 0x5412,

    HORIZONTAL_SEPARATION_PATTERN = [
        [0, 0, 0, 0, 0, 0, 0, 0]
    ],

    VERTICAL_SEPARATION_PATTERN = [
        [0], [0], [0], [0], [0], [0], [0]
    ],

    POSITION_DETECTION_PATTERN = [
        [1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1]
    ],

    POSITION_ADJUSTMENT_PATTERN = [
        [1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1],
        [1, 0, 1, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1]
    ],

    POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
        [-1, -1, -1, -1,  -1,  -1,  -1],  // Version 1
        [6, 18, -1, -1,  -1,  -1,  -1],  // Version 2
        [6, 22, -1, -1,  -1,  -1,  -1],  // Version 3
        [6, 26, -1, -1,  -1,  -1,  -1],  // Version 4
        [6, 30, -1, -1,  -1,  -1,  -1],  // Version 5
        [6, 34, -1, -1,  -1,  -1,  -1],  // Version 6
        [6, 22, 38, -1,  -1,  -1,  -1],  // Version 7
        [6, 24, 42, -1,  -1,  -1,  -1],  // Version 8
        [6, 26, 46, -1,  -1,  -1,  -1],  // Version 9
        [6, 28, 50, -1,  -1,  -1,  -1],  // Version 10
        [6, 30, 54, -1,  -1,  -1,  -1],  // Version 11
        [6, 32, 58, -1,  -1,  -1,  -1],  // Version 12
        [6, 34, 62, -1,  -1,  -1,  -1],  // Version 13
        [6, 26, 46, 66,  -1,  -1,  -1],  // Version 14
        [6, 26, 48, 70,  -1,  -1,  -1],  // Version 15
        [6, 26, 50, 74,  -1,  -1,  -1],  // Version 16
        [6, 30, 54, 78,  -1,  -1,  -1],  // Version 17
        [6, 30, 56, 82,  -1,  -1,  -1],  // Version 18
        [6, 30, 58, 86,  -1,  -1,  -1],  // Version 19
        [6, 34, 62, 90,  -1,  -1,  -1],  // Version 20
        [6, 28, 50, 72,  94,  -1,  -1],  // Version 21
        [6, 26, 50, 74,  98,  -1,  -1],  // Version 22
        [6, 30, 54, 78, 102,  -1,  -1],  // Version 23
        [6, 28, 54, 80, 106,  -1,  -1],  // Version 24
        [6, 32, 58, 84, 110,  -1,  -1],  // Version 25
        [6, 30, 58, 86, 114,  -1,  -1],  // Version 26
        [6, 34, 62, 90, 118,  -1,  -1],  // Version 27
        [6, 26, 50, 74,  98, 122,  -1],  // Version 28
        [6, 30, 54, 78, 102, 126,  -1],  // Version 29
        [6, 26, 52, 78, 104, 130,  -1],  // Version 30
        [6, 30, 56, 82, 108, 134,  -1],  // Version 31
        [6, 34, 60, 86, 112, 138,  -1],  // Version 32
        [6, 30, 58, 86, 114, 142,  -1],  // Version 33
        [6, 34, 62, 90, 118, 146,  -1],  // Version 34
        [6, 30, 54, 78, 102, 126, 150],  // Version 35
        [6, 24, 50, 76, 102, 128, 154],  // Version 36
        [6, 28, 54, 80, 106, 132, 158],  // Version 37
        [6, 32, 58, 84, 110, 136, 162],  // Version 38
        [6, 26, 54, 82, 110, 138, 166],  // Version 39
        [6, 30, 58, 86, 114, 142, 170]   // Version 40
    ];

class TECB extends TObject {
    constructor(count, dataCodewords) {
        super();
        this.Count = count;
        this.DataCodewords = dataCodewords;
    }

    GetCount() {
        return this.Count;
    }

    GetDataCodewords() {
        return this.DataCodewords;
    }
}

class TECBlocks extends TObject {
    constructor() {
        super();
        this.ECCodewordsPerBlock;
        this.ECBlocks;
    }

    static Create2(ECCodewordsPerBlock, ECBlocks) {
        let vECBlocks = new TECBlocks();
        vECBlocks.ECCodewordsPerBlock = ECCodewordsPerBlock;
        vECBlocks.ECBlocks = new TObjectArray(1);
        vECBlocks.ECBlocks[0] = ECBlocks;
        return vECBlocks;
    }

    static Create3(ECCodewordsPerBlock, ECBlocks1, ECBlocks2) {
        let vECBlocks = new TECBlocks();
        vECBlocks.ECCodewordsPerBlock = ECCodewordsPerBlock;
        vECBlocks.ECBlocks = new TObjectArray(2);
        vECBlocks.ECBlocks[0] = ECBlocks1;
        vECBlocks.ECBlocks[1] = ECBlocks2;
        return vECBlocks;
    }

    GetTotalECCodewords() {
        return this.ECCodewordsPerBlock * this.GetNumBlocks();
    }

    GetNumBlocks() {
        let vTotal = 0;
        for (let i = 0; i < this.ECBlocks.length; i++)
            vTotal += this.ECBlocks[i].GetCount();

        return vTotal;
    }

    GetECCodewordsPerBlock() {
        return this.ECCodewordsPerBlock;
    }

    GetECBlocks() {
        return this.ECBlocks;
    }
}

class TVersion extends TObject {
    constructor(versionNumber, alignmentPatternCenters,
        ECBlocks1, ECBlocks2, ECBlocks3, ECBlocks4) 
    {
        super();
        this.VersionNumber = versionNumber;
        this.AlignmentPatternCenters = alignmentPatternCenters;
        this.ECBlocks = new TObjectArray(4);
        this.ECBlocks[0] = ECBlocks1;
        this.ECBlocks[1] = ECBlocks2;
        this.ECBlocks[2] = ECBlocks3;
        this.ECBlocks[3] = ECBlocks4;

        this.TotalCodewords = 0;
        this.ECCodewords = ECBlocks1.GetECCodewordsPerBlock();
        let vECBArray = ECBlocks1.GetECBlocks();
        let vECBlock;
        for (let i = 0; i < vECBArray.length; i++) {
            vECBlock = vECBArray[i];
            this.TotalCodewords += vECBlock.GetCount() * (vECBlock.GetDataCodewords() + this.ECCodewords);
        }
    }

    static GetVersionForNumber(versionNum) {
        if (versionNum == 1)
            return new TVersion(1, [],
                TECBlocks.Create2(7, new TECB(1, 19)),
                TECBlocks.Create2(10, new TECB(1, 16)),
                TECBlocks.Create2(13, new TECB(1, 13)),
                TECBlocks.Create2(17, new TECB(1, 9))
                );
        else if (versionNum == 2)
            return new TVersion(2, [6, 18],
                TECBlocks.Create2(10, new TECB(1, 34)),
                TECBlocks.Create2(16, new TECB(1, 28)),
                TECBlocks.Create2(22, new TECB(1, 22)),
                TECBlocks.Create2(28, new TECB(1, 16))
                );
        else if (versionNum == 3)
            return new TVersion(3, [6, 22],
                TECBlocks.Create2(15, new TECB(1, 55)),
                TECBlocks.Create2(26, new TECB(1, 44)),
                TECBlocks.Create2(18, new TECB(2, 17)),
                TECBlocks.Create2(22, new TECB(2, 13))
                );
        else if (versionNum == 4)
            return new TVersion(4, [6, 26],
                TECBlocks.Create2(20, new TECB(1, 80)),
                TECBlocks.Create2(18, new TECB(2, 32)),
                TECBlocks.Create2(26, new TECB(2, 24)),
                TECBlocks.Create2(16, new TECB(4, 9))
                );
        else if (versionNum == 5)
            return new TVersion(5, [6, 30],
                TECBlocks.Create2(26, new TECB(1, 108)),
                TECBlocks.Create2(24, new TECB(2, 43)),
                TECBlocks.Create3(18, new TECB(2, 15), new TECB(2, 16)),
                TECBlocks.Create3(22, new TECB(2, 11), new TECB(2, 12))
                );
        else if (versionNum == 6)
            return new TVersion.Create(6, [6, 34],
                TECBlocks.Create2(18, new TECB(2, 68)),
                TECBlocks.Create2(16, new TECB(4, 27)),
                TECBlocks.Create2(24, new TECB(4, 19)),
                TECBlocks.Create2(28, new TECB(4, 15)));
        else
        if (versionNum == 7)
            return new TVersion.Create(7, [6, 22, 38],
                TECBlocks.Create2(20, new TECB(2, 78)),
                TECBlocks.Create2(18, new TECB(4, 31)),
                TECBlocks.Create3(18, new TECB(2, 14), new TECB(4, 15)),
                TECBlocks.Create3(26, new TECB(4, 13), new TECB(1, 14)));
        else if (versionNum == 8)
            return new TVersion.Create(8, [6, 24, 42],
                TECBlocks.Create2(24, new TECB(2, 97)),
                TECBlocks.Create3(22, new TECB(2, 38), new TECB(2, 39)),
                TECBlocks.Create3(22, new TECB(4, 18), new TECB(2, 19)),
                TECBlocks.Create3(26, new TECB(4, 14), new TECB(2, 15)));
        else if (versionNum == 9)
            return new TVersion.Create(9, [6, 26, 46],
                TECBlocks.Create2(30, new TECB(2, 116)),
                TECBlocks.Create3(22, new TECB(3, 36), new TECB(2, 37)),
                TECBlocks.Create3(20, new TECB(4, 16), new TECB(4, 17)),
                TECBlocks.Create3(24, new TECB(4, 12), new TECB(4, 13)));
        else if (versionNum == 10)
            return new TVersion.Create(10, [6, 28, 50],
                TECBlocks.Create3(18, new TECB(2, 68), new TECB(2, 69)),
                TECBlocks.Create3(26, new TECB(4, 43), new TECB(1, 44)),
                TECBlocks.Create3(24, new TECB(6, 19), new TECB(2, 20)),
                TECBlocks.Create3(28, new TECB(6, 15), new TECB(2, 16)));
        else if (versionNum == 11)
            return new TVersion.Create(11, [6, 30, 54],
                TECBlocks.Create2(20, new TECB(4, 81)),
                TECBlocks.Create3(30, new TECB(1, 50), new TECB(4, 51)),
                TECBlocks.Create3(28, new TECB(4, 22), new TECB(4, 23)),
                TECBlocks.Create3(24, new TECB(3, 12), new TECB(8, 13)));
        else if (versionNum == 12)
            return new TVersion.Create(12, [6, 32, 58],
                TECBlocks.Create3(24, new TECB(2, 92), new TECB(2, 93)),
                TECBlocks.Create3(22, new TECB(6, 36), new TECB(2, 37)),
                TECBlocks.Create3(26, new TECB(4, 20), new TECB(6, 21)),
                TECBlocks.Create3(28, new TECB(7, 14), new TECB(4, 15)));
        else if (versionNum == 13)
            return new TVersion.Create(13, [6, 34, 62],
                TECBlocks.Create2(26, new TECB(4, 107)),
                TECBlocks.Create3(22, new TECB(8, 37), new TECB(1, 38)),
                TECBlocks.Create3(24, new TECB(8, 20), new TECB(4, 21)),
                TECBlocks.Create3(22, new TECB(12, 11), new TECB(4, 12)));
        else if (versionNum == 14)
            return new TVersion.Create(14, [6, 26, 46, 66],
                TECBlocks.Create3(30, new TECB(3, 115), new TECB(1, 116)),
                TECBlocks.Create3(24, new TECB(4, 40), new TECB(5, 41)),
                TECBlocks.Create3(20, new TECB(11, 16), new TECB(5, 17)),
                TECBlocks.Create3(24, new TECB(11, 12), new TECB(5, 13)));
        else if (versionNum == 15)
            return new TVersion.Create(15, [6, 26, 48, 70],
                TECBlocks.Create3(22, new TECB(5, 87), new TECB(1, 88)),
                TECBlocks.Create3(24, new TECB(5, 41), new TECB(5, 42)),
                TECBlocks.Create3(30, new TECB(5, 24), new TECB(7, 25)),
                TECBlocks.Create3(24, new TECB(11, 12), new TECB(7, 13)));
        else
        if (versionNum == 16)
            return new TVersion.Create(16, [6, 26, 50, 74],
                TECBlocks.Create3(24, new TECB(5, 98), new TECB(1, 99)),
                TECBlocks.Create3(28, new TECB(7, 45), new TECB(3, 46)),
                TECBlocks.Create3(24, new TECB(15, 19), new TECB(2, 20)),
                TECBlocks.Create3(30, new TECB(3, 15), new TECB(13, 16)));
        else if (versionNum == 17)
            return new TVersion.Create(17, [6, 30, 54, 78],
                TECBlocks.Create3(28, new TECB(1, 107), new TECB(5, 108)),
                TECBlocks.Create3(28, new TECB(10, 46), new TECB(1, 47)),
                TECBlocks.Create3(28, new TECB(1, 22), new TECB(15, 23)),
                TECBlocks.Create3(28, new TECB(2, 14), new TECB(17, 15)));
        else if (versionNum == 18)
            return new TVersion.Create(18, [6, 30, 56, 82],
                TECBlocks.Create3(30, new TECB(5, 120), new TECB(1, 121)),
                TECBlocks.Create3(26, new TECB(9, 43), new TECB(4, 44)),
                TECBlocks.Create3(28, new TECB(17, 22), new TECB(1, 23)),
                TECBlocks.Create3(28, new TECB(2, 14), new TECB(19, 15)));
        else if (versionNum == 19)
            return new TVersion.Create(19, [6, 30, 58, 86],
                TECBlocks.Create3(28, new TECB(3, 113), new TECB(4, 114)),
                TECBlocks.Create3(26, new TECB(3, 44), new TECB(11, 45)),
                TECBlocks.Create3(26, new TECB(17, 21), new TECB(4, 22)),
                TECBlocks.Create3(26, new TECB(9, 13), new TECB(16, 14)));
        else if (versionNum == 20)
            return new TVersion.Create(20, [6, 34, 62, 90],
                TECBlocks.Create3(28, new TECB(3, 107), new TECB(5, 108)),
                TECBlocks.Create3(26, new TECB(3, 41), new TECB(13, 42)),
                TECBlocks.Create3(30, new TECB(15, 24), new TECB(5, 25)),
                TECBlocks.Create3(28, new TECB(15, 15), new TECB(10, 16)));
        else if (versionNum == 21)
            return new TVersion.Create(21, [6, 28, 50, 72, 94],
                TECBlocks.Create3(28, new TECB(4, 116), new TECB(4, 117)),
                TECBlocks.Create2(26, new TECB(17, 42)),
                TECBlocks.Create3(28, new TECB(17, 22), new TECB(6, 23)),
                TECBlocks.Create3(30, new TECB(19, 16), new TECB(6, 17)));
        else if (versionNum == 22)
            return new TVersion.Create(22, [6, 26, 50, 74, 98],
                TECBlocks.Create3(28, new TECB(2, 111), new TECB(7, 112)),
                TECBlocks.Create2(28, new TECB(17, 46)),
                TECBlocks.Create3(30, new TECB(7, 24), new TECB(16, 25)),
                TECBlocks.Create2(24, new TECB(34, 13)));
        else if (versionNum == 23)
            return new TVersion.Create(23, [6, 30, 54, 78, 102],
                TECBlocks.Create3(30, new TECB(4, 121), new TECB(5, 122)),
                TECBlocks.Create3(28, new TECB(4, 47), new TECB(14, 48)),
                TECBlocks.Create3(30, new TECB(11, 24), new TECB(14, 25)),
                TECBlocks.Create3(30, new TECB(16, 15), new TECB(14, 16)));
        else if (versionNum == 24)
            return new TVersion.Create(24, [6, 28, 54, 80, 106],
                TECBlocks.Create3(30, new TECB(6, 117), new TECB(4, 118)),
                TECBlocks.Create3(28, new TECB(6, 45), new TECB(14, 46)),
                TECBlocks.Create3(30, new TECB(11, 24), new TECB(16, 25)),
                TECBlocks.Create3(30, new TECB(30, 16), new TECB(2, 17)));
        else if (versionNum == 25)
            return new TVersion.Create(25, [6, 32, 58, 84, 110],
                TECBlocks.Create3(26, new TECB(8, 106), new TECB(4, 107)),
                TECBlocks.Create3(28, new TECB(8, 47), new TECB(13, 48)),
                TECBlocks.Create3(30, new TECB(7, 24), new TECB(22, 25)),
                TECBlocks.Create3(30, new TECB(22, 15), new TECB(13, 16)));
        else if (versionNum == 26)
            return new TVersion.Create(26, [6, 30, 58, 86, 114],
                TECBlocks.Create3(28, new TECB(10, 114), new TECB(2, 115)),
                TECBlocks.Create3(28, new TECB(19, 46), new TECB(4, 47)),
                TECBlocks.Create3(28, new TECB(28, 22), new TECB(6, 23)),
                TECBlocks.Create3(30, new TECB(33, 16), new TECB(4, 17)));
        else if (versionNum == 27)
            return new TVersion.Create(27, [6, 34, 62, 90, 118],
                TECBlocks.Create3(30, new TECB(8, 122), new TECB(4, 123)),
                TECBlocks.Create3(28, new TECB(22, 45), new TECB(3, 46)),
                TECBlocks.Create3(30, new TECB(8, 23), new TECB(26, 24)),
                TECBlocks.Create3(30, new TECB(12, 15), new TECB(28, 16)));
        else if (versionNum == 28)
            return new TVersion.Create(28, [6, 26, 50, 74, 98, 122],
                TECBlocks.Create3(30, new TECB(3, 117), new TECB(10, 118)),
                TECBlocks.Create3(28, new TECB(3, 45), new TECB(23, 46)),
                TECBlocks.Create3(30, new TECB(4, 24), new TECB(31, 25)),
                TECBlocks.Create3(30, new TECB(11, 15), new TECB(31, 16)));
        else if (versionNum == 29)
            return new TVersion.Create(29, [6, 30, 54, 78, 102, 126],
                TECBlocks.Create3(30, new TECB(7, 116), new TECB(7, 117)),
                TECBlocks.Create3(28, new TECB(21, 45), new TECB(7, 46)),
                TECBlocks.Create3(30, new TECB(1, 23), new TECB(37, 24)),
                TECBlocks.Create3(30, new TECB(19, 15), new TECB(26, 16)));
        else if (versionNum == 30)
            return new TVersion.Create(30, [6, 26, 52, 78, 104, 130],
                TECBlocks.Create3(30, new TECB(5, 115), new TECB(10, 116)),
                TECBlocks.Create3(28, new TECB(19, 47), new TECB(10, 48)),
                TECBlocks.Create3(30, new TECB(15, 24), new TECB(25, 25)),
                TECBlocks.Create3(30, new TECB(23, 15), new TECB(25, 16)));
        else if (versionNum == 31)
            return new TVersion.Create(31, [6, 30, 56, 82, 108, 134],
                TECBlocks.Create3(30, new TECB(13, 115), new TECB(3, 116)),
                TECBlocks.Create3(28, new TECB(2, 46), new TECB(29, 47)),
                TECBlocks.Create3(30, new TECB(42, 24), new TECB(1, 25)),
                TECBlocks.Create3(30, new TECB(23, 15), new TECB(28, 16)));
        else if (versionNum == 32)
            return new TVersion.Create(32, [6, 34, 60, 86, 112, 138],
                TECBlocks.Create2(30, new TECB(17, 115)),
                TECBlocks.Create3(28, new TECB(10, 46), new TECB(23, 47)),
                TECBlocks.Create3(30, new TECB(10, 24), new TECB(35, 25)),
                TECBlocks.Create3(30, new TECB(19, 15), new TECB(35, 16)));
        else if (versionNum == 33)
            return new TVersion.Create(33, [6, 30, 58, 86, 114, 142],
                TECBlocks.Create3(30, new TECB(17, 115), new TECB(1, 116)),
                TECBlocks.Create3(28, new TECB(14, 46), new TECB(21, 47)),
                TECBlocks.Create3(30, new TECB(29, 24), new TECB(19, 25)),
                TECBlocks.Create3(30, new TECB(11, 15), new TECB(46, 16)));
        else if (versionNum == 34)
            return new TVersion.Create(34, [6, 34, 62, 90, 118, 146],
                TECBlocks.Create3(30, new TECB(13, 115), new TECB(6, 116)),
                TECBlocks.Create3(28, new TECB(14, 46), new TECB(23, 47)),
                TECBlocks.Create3(30, new TECB(44, 24), new TECB(7, 25)),
                TECBlocks.Create3(30, new TECB(59, 16), new TECB(1, 17)));
        else if (versionNum == 35)
            return new TVersion.Create(35, [6, 30, 54, 78, 102, 126, 150],
                TECBlocks.Create3(30, new TECB(12, 121), new TECB(7, 122)),
                TECBlocks.Create3(28, new TECB(12, 47), new TECB(26, 48)),
                TECBlocks.Create3(30, new TECB(39, 24), new TECB(14, 25)),
                TECBlocks.Create3(30, new TECB(22, 15), new TECB(41, 16)));
        else if (versionNum == 36)
            return new TVersion.Create(36, [6, 24, 50, 76, 102, 128, 154],
                TECBlocks.Create3(30, new TECB(6, 121), new TECB(14, 122)),
                TECBlocks.Create3(28, new TECB(6, 47), new TECB(34, 48)),
                TECBlocks.Create3(30, new TECB(46, 24), new TECB(10, 25)),
                TECBlocks.Create3(30, new TECB(2, 15), new TECB(64, 16)));
        else if (versionNum == 37)
            return new TVersion.Create(37, [6, 28, 54, 80, 106, 132, 158],
                TECBlocks.Create3(30, new TECB(17, 122), new TECB(4, 123)),
                TECBlocks.Create3(28, new TECB(29, 46), new TECB(14, 47)),
                TECBlocks.Create3(30, new TECB(49, 24), new TECB(10, 25)),
                TECBlocks.Create3(30, new TECB(24, 15), new TECB(46, 16)));
        else if (versionNum == 38)
            return new TVersion.Create(38, [6, 32, 58, 84, 110, 136, 162],
                TECBlocks.Create3(30, new TECB(4, 122), new TECB(18, 123)),
                TECBlocks.Create3(28, new TECB(13, 46), new TECB(32, 47)),
                TECBlocks.Create3(30, new TECB(48, 24), new TECB(14, 25)),
                TECBlocks.Create3(30, new TECB(42, 15), new TECB(32, 16)));
        else if (versionNum == 39)
            return new TVersion.Create(39, [6, 26, 54, 82, 110, 138, 166],
                TECBlocks.Create3(30, new TECB(20, 117), new TECB(4, 118)),
                TECBlocks.Create3(28, new TECB(40, 47), new TECB(7, 48)),
                TECBlocks.Create3(30, new TECB(43, 24), new TECB(22, 25)),
                TECBlocks.Create3(30, new TECB(10, 15), new TECB(67, 16)));
        else if (versionNum == 40)
            return new TVersion.Create(40, [6, 30, 58, 86, 114, 142, 170],
                TECBlocks.Create3(30, new TECB(19, 118), new TECB(6, 119)),
                TECBlocks.Create3(28, new TECB(18, 47), new TECB(31, 48)),
                TECBlocks.Create3(30, new TECB(34, 24), new TECB(34, 25)),
                TECBlocks.Create3(30, new TECB(20, 15), new TECB(61, 16)));
        else
            return null;
    }

    static ChooseVersion(numInputBits, ECLevel) {
        let vVersion, vNumBytes, vECBlocks,
            vNumECBytes, vNumDataBytes, vTotalInputBytes;
        
        for (let i = 1; i <= 40; i++) {
            vVersion = TVersion.GetVersionForNumber(i);
            vNumBytes = vVersion.GetTotalCodewords();
            vECBlocks = vVersion.GetECBlocksForLevel(ECLevel);
            vNumECBytes = vECBlocks.GetTotalECCodewords();
            vNumDataBytes = vNumBytes - vNumECBytes;
            vTotalInputBytes = Math.trunc((numInputBits + 7) / 8);
      
            if (vNumDataBytes >= vTotalInputBytes)
                return vVersion;
        }

        return null;
    }

    GetTotalCodewords() {
        return this.TotalCodewords;
    }

    GetECBlocksForLevel(ECLevel) {
        return this.ECBlocks[ECLevel.Ordinal];
    }

    GetDimensionForVersion() {
        return 17 + 4 * this.VersionNumber;
    }
}

class TMaskUtil extends TObject {
    constructor() {
        super();
    }

    GetDataMaskBit(maskPattern, x, y) {
        let vIntermediate = 0, vTemp;
        if ((maskPattern >= 0) && (maskPattern < NUM_MASK_PATTERNS)) {
            switch (maskPattern) {
                case 0: 
                    vIntermediate = (y + x) & 1;
                    break;

                case 1:
                    vIntermediate = y & 1;
                    break;

                case 2:
                    vIntermediate = x % 3;
                    break;

                case 3:
                    vIntermediate = (y + x) % 3;
                    break;

                case 4:
                    vIntermediate = ((y >> 1) + Math.trunc(x / 3)) & 1;
                    break;

                case 5:
                    vTemp = y * x;
                    vIntermediate = (vTemp & 1) + (vTemp % 3);
                    break;

                case 6:
                    vTemp = y * x;
                    vIntermediate = ((vTemp & 1) + (vTemp & 3)) & 1;
                    break;

                case 7:
                    vTemp = y * x;
                    vIntermediate = ((vTemp % 3) + ((y + x) & 1)) & 1;
                    break;
            }
        }

        return vIntermediate == 0;
    }
}

class TMatrixUtil extends TObject {
    constructor() {
        super();
        this.MatrixUtilError = false;
    }

    ClearMatrix(matrix) {
        matrix.Clear(255);
    }

    EmbedBasicPatterns(version, matrix) {
        this.EmbedPositionDetectionPatternsAndSeparators(matrix);
        this.EmbedDarkDotAtLeftBottomCorner(matrix);
        this.MaybeEmbedPositionAdjustmentPatterns(version, matrix);
        this.EmbedTimingPatterns(matrix);
    }

    EmbedTypeInfo(ECLevel, maskPattern, matrix) {
        let vTypeInfoBits = new TBitArray(), vBit, vX1, vX2, vY1, vY2;
        this.MakeTypeInfoBits(ECLevel, maskPattern, vTypeInfoBits);
        
        for (let i = 0; i < vTypeInfoBits.GetSize(); i++) {
            vBit = vTypeInfoBits.Get(vTypeInfoBits.GetSize() - 1 - i);

            vX1 = TYPE_INFO_COORDINATES[i][0];
            vY1 = TYPE_INFO_COORDINATES[i][1];
            matrix.SetBoolean(vX1, vY1, vBit);
      
            if (i < 8) {
                vX2 = matrix.Width - i - 1;
                vY2 = 8;
                matrix.SetBoolean(vX2, vY2, vBit);
            } else {
                vX2 = 8;
                vY2 = matrix.Height - 7 + (i - 8);
                matrix.SetBoolean(vX2, vY2, vBit);
            }
        }
    }

    MaybeEmbedVersionInfo(version, matrix) {
        if (version < 7)
            return;
      
        let vVersionInfoBits = new TBitArray();
        this.MakeVersionInfoBits(version, vVersionInfoBits);
        let vBitIndex = 6 * 3 - 1, vBit;
        for (let i = 0; i < 6; i++) {
            for (let j = 0; j < 3; j++) {
                vBit = vVersionInfoBits.Get(vBitIndex);
                vBitIndex--;

                matrix.SetBoolean(i, matrix.Height - 11 + j, vBit);
                matrix.SetBoolean(matrix.Height - 11 + j, i, vBit);
            }
        }
    }

    EmbedDataBits(dataBits, maskPattern, matrix) {
        let vMaskUtil = new TMaskUtil(),
            vBitIndex = 0,
            vDirection = -1, vXX, vBit,
            vX = matrix.Width - 1,
            vY = matrix.Height - 1;

        while (vX > 0) {
            if (vX == 6)
                vX--;
        
            while ((vY >= 0) && (vY < matrix.Height)) {
                for (let i = 0; i < 2; i++) {
                    vXX = vX - i;
            
                    if (!this.IsEmpty(matrix.Get(vXX, vY)))
                        continue;
    
                    if (vBitIndex < dataBits.GetSize()) {
                        vBit = dataBits.Get(vBitIndex);
                        vBitIndex++;
                    } else
                        vBit = false;
    
                    if (maskPattern != -1) {
                        if (vMaskUtil.GetDataMaskBit(maskPattern, vXX, vY))
                            vBit = !vBit;
                    }

                    matrix.SetBoolean(vXX, vY, vBit);
                }

                vY += vDirection;
            }

            vDirection = -vDirection;
            vY += vDirection;
            vX -= 2;
        }

        if (vBitIndex != dataBits.GetSize())
            this.MatrixUtilError = true;
    }

    FindMSBSet(val) {
        let vNumDigits = 0;
        while (val != 0) {
            val = val >> 1;
            vNumDigits++;
        }

        return vNumDigits;
    }

    CalculateBCHCode(val, poly) {
        let vMSBSetInPoly = this.FindMSBSet(poly);
        val = val << (vMSBSetInPoly - 1);
        
        while (this.FindMSBSet(val) >= vMSBSetInPoly)
            val = val ^ (poly << (this.FindMSBSet(val) - vMSBSetInPoly));
        
        return val;
    }

    MakeTypeInfoBits(ECLevel, maskPattern, bits) {
        if ((maskPattern >= 0) && (maskPattern < NUM_MASK_PATTERNS)) {
            let vTypeInfo = (ECLevel.Bits << 3) | maskPattern;
            bits.AppendBits(vTypeInfo, 5);
      
            let vBCHCode = this.CalculateBCHCode(vTypeInfo, TYPE_INFO_POLY);
            bits.AppendBits(vBCHCode, 10);
      
            let vMaskBits = new TBitArray();
            vMaskBits.AppendBits(TYPE_INFO_MASK_PATTERN, 15);
            bits.XorOperation(vMaskBits);
      
            if (bits.GetSize() != 15) {
                this.MatrixUtilError = true;
                return;
            }
        }
    }

    MakeVersionInfoBits(version, bits) {
        bits.AppendBits(version, 6);
        let vBCHCode = this.CalculateBCHCode(version, VERSION_INFO_POLY);
        bits.AppendBits(vBCHCode, 12);
      
        if (bits.GetSize() != 18)
            this.MatrixUtilError = true;
    }

    IsEmpty(val) {
        return val == -1
    }

    EmbedTimingPatterns(matrix) {
        let vBit;
        for (let i = 8; i <= matrix.Width - 9; i++) {
            vBit = (i + 1) % 2;
    
            if (this.IsEmpty(matrix.Get(i, 6)))
                matrix.SetInteger(i, 6, vBit);
    
            if (this.IsEmpty(matrix.Get(6, i)))
                matrix.SetInteger(6, i, vBit);
        }
    }

    EmbedDarkDotAtLeftBottomCorner(matrix) {
        if (matrix.Get(8, matrix.Height - 8) == 0) {
            this.MatrixUtilError = true;
            return;
        }
        
        matrix.SetInteger(8, matrix.Height - 8, 1);
    }

    EmbedHorizontalSeparationPattern(xStart, yStart, matrix) {
        for (let x = 0; x < 8; x++) {
            if (!this.IsEmpty(matrix.Get(xStart + x, yStart))) {
                this.MatrixUtilError = true;
                return;
            }
          
            matrix.SetInteger(xStart + x, yStart, HORIZONTAL_SEPARATION_PATTERN[0][x]);
        }
    }

    EmbedVerticalSeparationPattern(xStart, yStart, matrix) {
        for (let y = 0; y < 7; y++) {
            if (!this.IsEmpty(matrix.Get(xStart, yStart + y))) {
                this.MatrixUtilError = true;
                return;
            }
          
            matrix.SetInteger(xStart, yStart + y, VERTICAL_SEPARATION_PATTERN[y][0]);
        }
    }

    EmbedPositionAdjustmentPattern(xStart, yStart, matrix) {
        for (let y = 0; y < 5; y++) {
            for (let x = 0; x < 5; x++) {
                if (!this.IsEmpty(matrix.Get(xStart + x, yStart + y))) {
                    this.MatrixUtilError = true;
                    return;
                }

                matrix.SetInteger(xStart + x, yStart + y, POSITION_ADJUSTMENT_PATTERN[y][x]);
            }
        }
    }

    EmbedPositionDetectionPattern(xStart, yStart, matrix) {
        for (let y = 0; y < 7; y++) {
            for (let x = 0; x < 7; x++) {
                if (!this.IsEmpty(matrix.Get(xStart + x, yStart + y))) {
                    this.MatrixUtilError = true;
                    return;
                }

                matrix.SetInteger(xStart + x, yStart + y, POSITION_DETECTION_PATTERN[y][x]);
            }
        }
    }

    EmbedPositionDetectionPatternsAndSeparators(matrix) {
        let vPDPWidth = POSITION_DETECTION_PATTERN[0].length;
        this.EmbedPositionDetectionPattern(0, 0, matrix);
        this.EmbedPositionDetectionPattern(matrix.Width - vPDPWidth, 0, matrix);
        this.EmbedPositionDetectionPattern(0, matrix.Width- vPDPWidth, matrix);
      
        let vHSPWidth = HORIZONTAL_SEPARATION_PATTERN[0].length;
        this.EmbedHorizontalSeparationPattern(0, vHSPWidth - 1, matrix);
        this.EmbedHorizontalSeparationPattern(matrix.Width - vHSPWidth, vHSPWidth - 1, matrix);
        this.EmbedHorizontalSeparationPattern(0, matrix.Width - vHSPWidth, matrix);
      
        let vVSPSize = VERTICAL_SEPARATION_PATTERN.length;
        this.EmbedVerticalSeparationPattern(vVSPSize, 0, matrix);
        this.EmbedVerticalSeparationPattern(matrix.Height - vVSPSize - 1, 0, matrix);
        this.EmbedVerticalSeparationPattern(vVSPSize, matrix.Height - vVSPSize, matrix);
    }

    MaybeEmbedPositionAdjustmentPatterns(version, matrix) {
        if (version >= 2) {
            let vIndex = version - 1;
            let vNumCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[vIndex].length;
            let vCoordinates = new TIntegerArray(vNumCoordinates);
            for (let i = 0; i < vNumCoordinates; i++)
                vCoordinates[i] = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[vIndex][i];

            let x, y;
            for (let i = 0; i < vNumCoordinates; i++) {
                for (let j = 0; j < vNumCoordinates; j++) {
                    y = vCoordinates[i];
                    x = vCoordinates[j];
                    if ((x == -1) || (y == -1))
                        continue;

                    if (this.IsEmpty(matrix.Get(x, y)))
                        this.EmbedPositionAdjustmentPattern(x - 2, y - 2, matrix);   
                }
            }
        }
    }

    BuildMatrix(dataBits, ECLevel, version, maskPattern, matrix) {
        this.ClearMatrix(matrix);
        this.EmbedBasicPatterns(version, matrix);
        this.EmbedTypeInfo(ECLevel, maskPattern, matrix);
        this.MaybeEmbedVersionInfo(version, matrix);
        this.EmbedDataBits(dataBits, maskPattern, matrix);
    }
}

class TGenericGF extends TObject {
    constructor(Primitive, Size, B) {
        super();
        this.FInitialized = false;
        this.FPrimitive = Primitive;
        this.FSize = Size;
        this.FGeneratorBase = B;
        this.FPolyList = new TObjectArray(0);
        if (this.FSize < 0)
            this.Initialize();
    }

    CheckInit() {
        if (!this.FInitialized)
            this.Initialize();
    }

    Initialize() {
        this.FExpTable = new TIntegerArray(this.FSize);
        this.FLogTable = new TIntegerArray(this.FSize);
        let x = 1;
        for (let i = 0; i < this.FSize; i++) {
            this.FExpTable[i] = x;
            x = x << 1;
            if (x >= this.FSize) {
                x = x ^ this.FPrimitive;
                x = x & (this.FSize - 1);
            }
        }

        for (let i = 0; i < this.FSize - 1; i++)
            this.FLogTable[this.FExpTable[i]] = i;

        let vCA = new TIntegerArray(1);
        vCA[0] = 0;
        this.FZero = new TGenericGFPoly(this, vCA);

        vCA = new TIntegerArray(1);
        vCA[0] = 1;
        this.FOne = new TGenericGFPoly(this, vCA);

        this.FInitialized = true;
    }

    static CreateQRCodeField256() {
        return new TGenericGF(0x011D, 256, 0);
    }

    static AddOrSubtract(A, B) {
        return A ^ B;
    }

    GetZero() {
        this.CheckInit();
        return this.FZero;
    }

    Exp(A) {
        this.CheckInit();
        return this.FExpTable[A];
    }

    GetGeneratorBase() {
        return this.FGeneratorBase;
    }

    Inverse(A) {
        this.CheckInit();
        if (A != 0)
            return this.FExpTable[this.FSize - this.FLogTable[A] - 1];
        else
            return 0;
    }

    Multiply(A, B) {
        this.CheckInit();
        if (A != 0 && B != 0)
            return this.FExpTable[(this.FLogTable[A] + this.FLogTable[B]) % (this.FSize - 1)];
        else
            return 0;
    }

    BuildMonomial(degree, coefficient) {
        this.CheckInit();
        if (degree >= 0) {
            if (coefficient == 0)
                return this.FZero;

            let vCoefficients = new TIntegerArray(degree + 1);
            vCoefficients[0] = coefficient;
            return new TGenericGFPoly(this, vCoefficients);
        } else
            return null;
    }
}

class TGenericGFPoly extends TObject {
    constructor(field, coefficients) {
        super();
        this.Coefficients = coefficients;
        this.FField = field;
        this.FField.FPolyList.length = this.FField.FPolyList.length + 1;
        this.FField.FPolyList[this.FField.FPolyList.length - 1] = this;
        let vCoefficientsLength = coefficients.length;
        if (vCoefficientsLength > 1 && coefficients[0] == 0) {
            let vFirstNonZero = 1;
            while (vFirstNonZero < vCoefficientsLength && coefficients[vFirstNonZero] == 0)
                vFirstNonZero--;

            if (vFirstNonZero == vCoefficientsLength)
                this.Coefficients = field.GetZero().Coefficients;
            else {
                let vLen = vCoefficientsLength - vFirstNonZero;
                this.Coefficients = new TIntegerArray(vLen);
                for (let i = 0; i < vLen; i++)
                    this.Coefficients[i] = coefficients[vFirstNonZero + i];
            }
        }
    }

    Multiply(other) {
        let vResult = null, vACoefficients, vBCoefficients, vALength, vBLength;
        
        if (this.FField == other.FField) {
            if (this.IsZero() || other.IsZero())
                return this.FField.GetZero();

            vACoefficients = this.Coefficients.concat();
            vALength = vACoefficients.length;
            vBCoefficients = other.Coefficients.concat();
            vBLength = vBCoefficients.length;

            let vProduct = new TIntegerArray(vALength + vBLength - 1), vACoeff = 0;
            for (let i = 0; i < vALength; i++) {
                vACoeff = vACoefficients[i];
                for (let j = 0; j < vBLength; j++) {
                    vProduct[i + j] = TGenericGF.AddOrSubtract(vProduct[i + j],
                        this.FField.Multiply(vACoeff, vBCoefficients[j]));
                }
            }

            return new TGenericGFPoly(this.FField, vProduct);
        }

        return vResult;
    }

    MultiplyByMonomial(degree, coefficient) {
        if (degree >= 0) {
            if (coefficient == 0)
                return this.FField.GetZero();

            let vSize = this.Coefficients.length;
            let vProduct = new TIntegerArray(vSize + degree);
            for (let i = 0; i < vSize; i++)
                vProduct[i] = this.FField.Multiply(this.Coefficients[i], coefficient);

            return new TGenericGFPoly(this.FField, vProduct);
        }
    }

    Divide(other) {
        let vResult = null;
        if (this.FField == other.FField && (!other.IsZero())) {
            let vQuotient = this.FField.GetZero();
            let vRemainder = this;

            let vDenominatorLeadingTerm = other.GetCoefficient(other.GetDegree());
            let vInverseDenominatorLeadingTerm = this.FField.Inverse(vDenominatorLeadingTerm);
            let vDegreeDifference, vScale, vTerm, vIterationQuotient;
            while (vRemainder.GetDegree() >= other.GetDegree() && (!vRemainder.IsZero())) {
                vDegreeDifference = vRemainder.GetDegree() - other.GetDegree();
                vScale = this.FField.Multiply(vRemainder.GetCoefficient(vRemainder.GetDegree()), vInverseDenominatorLeadingTerm);
                vTerm = other.MultiplyByMonomial(vDegreeDifference, vScale);
                vIterationQuotient = this.FField.BuildMonomial(vDegreeDifference, vScale);
                vQuotient = vQuotient.AddOrSubtract(vIterationQuotient);
                vRemainder = vRemainder.AddOrSubtract(vTerm);
            }

            vResult = new TObjectArray(2);
            vResult[0] = vQuotient;
            vResult[1] = vRemainder;
        }

        return vResult;
    }

    GetCoefficients() {
        return this.Coefficients;
    }

    IsZero() {
        return this.Coefficients[0] == 0;
    }

    GetCoefficient(degree) {
        return this.Coefficients[this.Coefficients.length - 1 - degree];
    }

    GetDegree() {
        return this.Coefficients.length - 1;
    }

    AddOrSubtract(other) {
        let vResult = null;
        if (other != null) {
            if (this.FField === other.FField) {
                if (this.IsZero()) 
                    return other;
      
                if (other.IsZero())
                    return this;
      
                let vSmallerCoefficients = this.Coefficients.concat();
                let vLargerCoefficients = other.Coefficients.concat();
                if (vSmallerCoefficients.length > vLargerCoefficients.length) {
                    let vTemp = vSmallerCoefficients.concat();
                    vSmallerCoefficients = vLargerCoefficients;
                    vLargerCoefficients = vTemp;
                }

                let vSumDiff = new TIntegerArray(vLargerCoefficients.length);
                let vLengthDiff = vLargerCoefficients.length - vSmallerCoefficients.length;
                if (vLengthDiff > 0)
                    vSumDiff = vLargerCoefficients.concat();
      
                for (let i = vLengthDiff, vLen = vLargerCoefficients.length; i < vLen; i++)
                    vSumDiff[i] = TGenericGF.AddOrSubtract(vSmallerCoefficients[i - vLengthDiff], vLargerCoefficients[i]);
      
                return new TGenericGFPoly(this.FField, vSumDiff);
            }
        }

        return vResult;
    }
}

class TReedSolomonEncoder extends TObject {
    constructor(field) {
        super();
        this.FField = field;
        this.FCachedGenerators = new TList();
        let vIntArray = new TIntegerArray(1);
        vIntArray[0] = 1;
        let vGenericGFPoly = new TGenericGFPoly(field, vIntArray);
        this.FCachedGenerators.add(vGenericGFPoly);
    }

    Encode(toEncode, ECBytes) {
        if (ECBytes > 0) {
            let vDataBytes = toEncode.length - ECBytes;
            if (vDataBytes > 0) {
                let vGenerator = this.BuildGenerator(ECBytes);
                let vInfoCoefficients = new TIntegerArray(vDataBytes);
                for (let i = 0; i < vDataBytes; i++)
                    vInfoCoefficients[i] = toEncode[i];

                let vInfo = new TGenericGFPoly(this.FField, vInfoCoefficients);
                vInfo = vInfo.MultiplyByMonomial(ECBytes, 1);
                let vRemainder = vInfo.Divide(vGenerator)[1];
                let vCoefficients = vRemainder.GetCoefficients();
                let vNumZeroCoefficients = ECBytes - vCoefficients.length;
                for (let i = 0; i < vNumZeroCoefficients; i++)
                    toEncode[vDataBytes + i] = 0;

                for (let i = 0, vLen = vCoefficients.length; i < vLen; i++)
                    toEncode[vDataBytes + vNumZeroCoefficients + i] = vCoefficients[i];
            }
        }
    }

    BuildGenerator(degree) {
        if (degree >= this.FCachedGenerators.count) {
            let vLastGenerator = this.FCachedGenerators[this.FCachedGenerators.count - 1];
            let vCA = new TIntegerArray(2), vPoly, vNextGenerator;
            for (let D = this.FCachedGenerators.count; D <= degree; D++) {
                vCA[0] = 1;
                vCA[1] = this.FField.Exp(D - 1 + this.FField.GetGeneratorBase());
                vPoly = new TGenericGFPoly(this.FField, vCA);
                vNextGenerator = vLastGenerator.Multiply(vPoly);
                this.FCachedGenerators.add(vNextGenerator);
                vLastGenerator = vNextGenerator;
            }
        }

        return this.FCachedGenerators[degree];
    }
}

class TBlockPair extends TObject {
    constructor(BA1, BA2) {
        super();
        this.FDataBytes = BA1;  // TByteArray
        this.FErrorCorrectionBytes = BA2;  // TByteArray
    }

    GetDataBytes() {
        return this.FDataBytes;
    }

    GetErrorCorrectionBytes() {
        return this.FErrorCorrectionBytes;
    }
}

class TEncoder extends TObject {
    constructor() {
        super();
        this.EncoderError = false;
    }

    ApplyMaskPenaltyRule1Internal(matrix, isHorizontal) {
        let vPenalty = 0, vNumSameBitCells = 0, vPrevBit = -1, vILimit, vJLimit;
        if (isHorizontal) {
            vILimit = matrix.Height;
            vJLimit = matrix.Width;
        } else {
            vILimit = matrix.Width;
            vJLimit = matrix.Height;
        }

        let vTheArray = matrix.GetArray(), vBit;
      
        for (let i = 0; i < vILimit; i++) {
            for (let j = 0; j < vJLimit; j++) {
                if (isHorizontal)
                    vBit = vTheArray[i][j];
                else
                    vBit = vTheArray[j][i];

                if (vBit == vPrevBit) {
                    vNumSameBitCells++;

                    if (vNumSameBitCells == 5)
                        vPenalty += 3;
                    else if (vNumSameBitCells > 5)
                        vPenalty++;
                } else {
                    vNumSameBitCells = 1;  // Include the cell itself.
                    vPrevBit = vBit;
                }
            }
          
            vNumSameBitCells = 0;  // Clear at each row/column.
        }

        return vPenalty;
    }

    ChooseMode(content, encodeOptions) {
        let vAllNumeric = true, vAllAlphanumeric = true, vAllISO = true, vC;
        if (encodeOptions == 0) {
            for (let i = 0; i < content.length; i++) {
                vC = content[i];
                if (vC < "0" || vC > "9") {
                    vAllNumeric = false;
                    break;
                }
            }

            if (!vAllNumeric) {
                for (let i = 0; i < content.length; i++) {
                    vC = content[i];
                    if (this.GetAlphanumericCode(vC.charCodeAt(0)) < 0) {
                        vAllAlphanumeric = false;
                        break;
                    }
                }
            } else
                vAllAlphanumeric = false;

            if (!vAllAlphanumeric) {
                for (let i = 0; i < content.length; i++) {
                    vC = content[i];
                    if (vC.charCodeAt(0) > 0xFF) {
                        vAllISO = false;
                        break;
                    }
                }
            } else
                vAllISO = false;

            if (vAllNumeric)
                return {
                    mode: TQRMode.Numeric,
                    encoding: encodeOptions
                }
            else if (vAllAlphanumeric)
                return {
                    mode: TQRMode.Alphanumeric,
                    encoding: encodeOptions
                }
            else if (vAllISO) {
                return {
                    mode: TQRMode.Byte,
                    encoding: 3
                }
            } else
                return {
                    mode: TQRMode.Byte,
                    encoding: 4
                }
        } else if (encodeOptions == 1)
            return {
                mode: TQRMode.Numeric,
                encoding: encodeOptions
            }
        else if (encodeOptions == 2)
            return {
                mode: TQRMode.Alphanumeric,
                encoding: encodeOptions
            }
        else
            return {
                mode: TQRMode.Byte,
                encoding: encodeOptions
            }
    }


    FilterContent(content, mode, encodeOptions) {
        let vResult = "", vCanAdd = false;
        for (let i = 0; i < content.length; i++) {
            vCanAdd = false;
            if (mode == TQRMode.Numeric)
                vCanAdd = content[i] >= "0" && content[i] <= "9";
            else if (mode == TQRMode.Alphanumeric)
                vCanAdd = this.GetAlphanumericCode(content[i].charCodeAt(0)) > 0;
            else if (mode == TQRMode.Byte) {
                if (encodeOptions == 3)
                    vCanAdd = content[i].charCodeAt(0) <= 0xFF;
                else if (encodeOptions == 4 || encodeOptions == 5)
                    vCanAdd = true;
            }

            if (vCanAdd)
                vResult += content[i];
        }

        return vResult;
    }

    Append8BitBytes(content, bits, encodeOptions) {
        let vBytes = null;
        if (encodeOptions == 3) {
            vBytes = new Uint8Array(content.length);
            for (let i = 0; i < content.length; i++)
                vBytes[i] = content[i].charCodeAt(0) & 0xFF;
        } else if (encodeOptions == 4) {
            let vArr = TUtf8Encoding.getBytes(content);
            vBytes = new Uint8Array(vArr.length + 3);
            vBytes[0] = 0xEF;
            vBytes[1] = 0xBB;
            vBytes[2] = 0xBF;
            for (let i = 0; i < vArr.length; i++)
                vBytes[3 + i] = vArr[i];
        } else if (encodeOptions == 5) {
            vBytes = TUtf8Encoding.getBytes(content);
        }

        if (vBytes != null) {
            for (let i = 0; i < vBytes.length; i++)
                bits.AppendBits(vBytes[i], 8);
        }
    }
  
    AppendAlphanumericBytes(content, bits) {
        let vContentLength = content.length, i = 0, vCode1, vCode2;
        while (i < vContentLength) {
            vCode1 = this.GetAlphanumericCode(content[i].charCodeAt(0));
            if (vCode1 == -1) {
                this.EncoderError = true;
                return;
            }

            if (i + 1 < vContentLength) {
                vCode2 = this.GetAlphanumericCode(content[i + 1].charCodeAt(0));
                if (vCode2 == -1) {
                    this.EncoderError = true;
                    return;
                }

                bits.AppendBits(vCode1 * 45 + vCode2, 11);
                i += 2;
            } else {
                bits.AppendBits(vCode1, 6);
                i++;
            }
        }
    }

    AppendBytes(content, mode, bits, encodeOptions) {
        if (mode == TQRMode.Numeric)
            this.AppendNumericBytes(content, bits);
        else if (mode == TQRMode.Alphanumeric)
            this.AppendAlphanumericBytes(content, bits);
        else if (mode == TQRMode.Byte)
            this.Append8BitBytes(content, bits, encodeOptions);
        else if (mode == TQRMode.Kanji)
            this.AppendKanjiBytes()
        else
            this.EncoderError = true;
    }

    AppendKanjiBytes() {

    }

    AppendLengthInfo(numLetters, versionNum, mode, bits) {
        let vVersion = TVersion.GetVersionForNumber(versionNum);
        let vNumBits = this.GetModeCharacterCountBits(mode, vVersion);

        if (numLetters > ((1 << vNumBits) - 1)) {
            this.EncoderError = true;
            return;
        }

        bits.AppendBits(numLetters, vNumBits);
    }

    GetModeBits(mode) {
        return ModeBits[mode];
    }

    AppendModeInfo(mode, bits) {
        bits.AppendBits(this.GetModeBits(mode), 4);
    }

    AppendNumericBytes(content, bits) {
        let vContentLength = content.length, i = 0, vNum1, vNum2, vNum3;
        while (i < vContentLength) {
            vNum1 = content[i].charCodeAt(0) - "0".charCodeAt(0);
            if (i + 2 < vContentLength) {
                vNum2 = content[i + 1].charCodeAt(0) - "0".charCodeAt(0);
                vNum3 = content[i + 2].charCodeAt(0) - "0".charCodeAt(0);
                bits.AppendBits(vNum1 * 100 + vNum2 * 10 + vNum3, 10);
                i += 3;
            } else if (i + 1 < vContentLength) {
                vNum2 = content[i + 1].charCodeAt(0) - "0".charCodeAt(0);
                bits.AppendBits(vNum1 * 10 + vNum2, 7);
                i += 2;
            } else {
                bits.AppendBits(vNum1, 4);
                i++;
            }
        }
    }

    ChooseMaskPattern(bits, ECLevel, version, matrix) {
        let vMinPenalty = 2147483647, vBestMaskPattern = -1, vMatrixUtil, vPenalty;
        for (let i = 0; i < NUM_MASK_PATTERNS; i++) {
            vMatrixUtil = new TMatrixUtil();
            vMatrixUtil.BuildMatrix(bits, ECLevel, version, i, matrix);
            vPenalty = this.CalculateMaskPenalty(matrix);
            if (vPenalty < vMinPenalty) {
                vMinPenalty = vPenalty;
                vBestMaskPattern = i;
            }
        }

        return vBestMaskPattern;
    }

    GenerateECBytes(dataBytes, numECBytesInBlock) {
        let vNumDataBytes = dataBytes.length;
        let vToEncode = new TIntegerArray(vNumDataBytes + numECBytesInBlock);
        for (let i = 0; i < vNumDataBytes; i++)
            vToEncode[i] = dataBytes[i] & 0xFF;

        let vGenericGF = TGenericGF.CreateQRCodeField256();
        let vReedSolomonEncoder = new TReedSolomonEncoder(vGenericGF);
        vReedSolomonEncoder.Encode(vToEncode, numECBytesInBlock);

        let vECBytes = new Uint8Array(numECBytesInBlock);
        for (let i = 0; i < numECBytesInBlock; i++)
            vECBytes[i] = vToEncode[vNumDataBytes + i];

        return vECBytes;
    }

    GetAlphanumericCode(code) {
        if (code < ALPHANUMERIC_TABLE.length)
            return ALPHANUMERIC_TABLE[code];
        else
            return -1;
    }

    GetNumDataBytesAndNumECBytesForBlockID(numTotalBytes, numDataBytes, numRSBlocks, blockID, numDataBytesInBlock, numECBytesInBlock) {
        if (blockID >= numRSBlocks) {
            this.EncoderError = true;
            return;
        }

        // numRsBlocksInGroup2 = 196 % 5 = 1
        let NumRSBlocksInGroup2 = numTotalBytes % numRSBlocks,
        // numRsBlocksInGroup1 = 5 - 1 = 4
        NumRSBlocksInGroup1 = numRSBlocks - NumRSBlocksInGroup2,
        // numTotalBytesInGroup1 = 196 / 5 = 39
        NumTotalBytesInGroup1 = Math.trunc(numTotalBytes / numRSBlocks),
        // numTotalBytesInGroup2 = 39 + 1 = 40
        NumTotalBytesInGroup2 = NumTotalBytesInGroup1 + 1,
        // numDataBytesInGroup1 = 66 / 5 = 13
        NumDataBytesInGroup1 = Math.trunc(numDataBytes / numRSBlocks),
        // numDataBytesInGroup2 = 13 + 1 = 14
        NumDataBytesInGroup2 = NumDataBytesInGroup1 + 1,
        // numEcBytesInGroup1 = 39 - 13 = 26
        NumECBytesInGroup1 = NumTotalBytesInGroup1 - NumDataBytesInGroup1,
        // numEcBytesInGroup2 = 40 - 14 = 26
        NumECBytesInGroup2 = NumTotalBytesInGroup2 - NumDataBytesInGroup2;
        // Sanity checks.
        // 26 = 26
        if (NumECBytesInGroup1 != NumECBytesInGroup2) {
            this.EncoderError = true;
            return;
        }
        // 5 = 4 + 1.
        if (numRSBlocks != (NumRSBlocksInGroup1 + NumRSBlocksInGroup2)) {
            this.EncoderError = true;
            return;
        }
        // 196 = (13 + 26) * 4 + (14 + 26) * 1
        if (numTotalBytes !=
            ((NumDataBytesInGroup1 + NumECBytesInGroup1) * NumRSBlocksInGroup1) +
            ((NumDataBytesInGroup2 + NumECBytesInGroup2) * NumRSBlocksInGroup2))
        {
            this.EncoderError = true;
            return;
        }
      
        if (blockID < NumRSBlocksInGroup1) {
            numDataBytesInBlock[0] = NumDataBytesInGroup1;
            numECBytesInBlock[0] = NumECBytesInGroup1;
        } else {
            numDataBytesInBlock[0] = NumDataBytesInGroup2;
            numECBytesInBlock[0] = NumECBytesInGroup2;
        }
    }

    InterleaveWithECBytes(bits, numTotalBytes, numDataBytes, numRSBlocks, result) {
        // "bits" must have "getNumDataBytes" bytes of data.
        if (bits.GetSizeInBytes() != numDataBytes) {
            this.EncoderError = true;
            return;
        }

        // Step 1.  Divide data bytes into blocks and generate error correction bytes for them. We'll
        // store the divided data bytes blocks and error correction bytes blocks into "blocks".
        let vDataBytesOffset = 0, vMaxNumDataBytes = 0, vMaxNumECBytes = 0,
            vSize, vDataBytes, vECBytes, vBlockPair;
        let vNumDataBytesInBlock = new TIntegerArray(1);
        let vNumECBytesInBlock = new TIntegerArray(1);
        // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number.
        let vBlocks = new TList();
        for (let i = 0; i < numRSBlocks; i++) {
            this.GetNumDataBytesAndNumECBytesForBlockID(
                numTotalBytes, numDataBytes, numRSBlocks, i,
                vNumDataBytesInBlock, vNumECBytesInBlock);

            vSize = vNumDataBytesInBlock[0];
            vDataBytes = new TByteArray(vSize);
            bits.ToBytes(8 * vDataBytesOffset, vDataBytes, 0, vSize);
            vECBytes = this.GenerateECBytes(vDataBytes, vNumECBytesInBlock[0]);
            vBlockPair = new TBlockPair(vDataBytes, vECBytes);
            vBlocks.add(vBlockPair);

            vMaxNumDataBytes = Math.max(vMaxNumDataBytes, vSize);
            vMaxNumECBytes = Math.max(vMaxNumECBytes, vECBytes.length);
            vDataBytesOffset += vNumDataBytesInBlock[0];
        }

        if (numDataBytes != vDataBytesOffset) {
            this.EncoderError = true;
            return;
        }

        // First, place data blocks.
        for (let i = 0; i < vMaxNumDataBytes; i++) {
            for (let j = 0; j < vBlocks.count; j++) {
                vDataBytes = vBlocks[j].GetDataBytes();
                if (i < vDataBytes.length)
                    result.AppendBits(vDataBytes[i], 8);
            }
        }

        // Then, place error correction blocks.
        for (let i = 0; i < vMaxNumECBytes; i++) {
            for (let j = 0; j < vBlocks.count; j++) {
                vECBytes = vBlocks[j].GetErrorCorrectionBytes();
                if (i < vECBytes.length)
                    result.AppendBits(vECBytes[i], 8);
            }
        }

        if (numTotalBytes != result.GetSizeInBytes())  // Should be same.
            this.EncoderError = true;
    }

    TerminateBits(numDataBytes, bits) {
        let vCapacity = numDataBytes << 3;
        if (bits.GetSize() > vCapacity) {
            this.FEncoderError = true;
            return;
        }

        let i = 0;
        while ((i < 4) && (bits.GetSize() < vCapacity)) {
            bits.AppendBit(false);
            i++;
        }
      
        // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details.
        // If the last byte isn't 8-bit aligned, we'll add padding bits.
        let vNumBitsInLastByte = bits.GetSize() & 0x07;
        if (vNumBitsInLastByte > 0) {
            for (let i = vNumBitsInLastByte; i < 8; i++)
                bits.AppendBit(false);
        }
      
        // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).
        let vNumPaddingBytes = numDataBytes - bits.GetSizeInBytes();
        for (let i = 0; i < vNumPaddingBytes; i++) {
            if ((i & 0x01) == 0)
                bits.AppendBits(0xEC, 8);
            else
                bits.AppendBits(0x11, 8);
        }

        if (bits.GetSize() != vCapacity)
            this.EncoderError = true;
    }

    CalculateMaskPenalty(matrix) {
        let vPenalty = 0;
        vPenalty += this.ApplyMaskPenaltyRule1(matrix);
        vPenalty += this.ApplyMaskPenaltyRule2(matrix);
        vPenalty += this.ApplyMaskPenaltyRule3(matrix);
        vPenalty += this.ApplyMaskPenaltyRule4(matrix);
        return vPenalty;
    }

    ApplyMaskPenaltyRule1(matrix) {
        return this.ApplyMaskPenaltyRule1Internal(matrix, true)
            + this.ApplyMaskPenaltyRule1Internal(matrix, false);
    }

    ApplyMaskPenaltyRule2(matrix) {
        let vPenalty = 0, vValue;
        let vTheArray = matrix.GetArray();
        let vWidth = matrix.Width;
        let vHeight = matrix.Height;
        for (let y = 0; y < vHeight - 1; y++) {
            for (let x = 0; x < vWidth - 1; x++) {
                vValue = vTheArray[y][x];
                if ((vValue == vTheArray[y][x + 1])
                    && (vValue == vTheArray[y + 1][x])
                    && (vValue == vTheArray[y + 1][x + 1]))

                    vPenalty += 3;
            }
        }

        return vPenalty;
    }

    ApplyMaskPenaltyRule3(matrix) {
        let vPenalty = 0,
            vTheArray = matrix.GetArray(),
            vWidth = matrix.Width,
            vHeight = matrix.Height;

        for (let y = 0; y < vHeight; y++) {
            for (let x = 0; x < vWidth; x++) {
                if (
                        (x + 6 < vWidth)
                        && (vTheArray[y][x] == 1)
                        && (vTheArray[y][x + 1] == 0)
                        && (vTheArray[y][x + 2] == 1)
                        && (vTheArray[y][x + 3] == 1)
                        && (vTheArray[y][x + 4] == 1)
                        && (vTheArray[y][x + 5] == 0)
                        && (vTheArray[y][x + 6] == 1)
                        && (
                                (
                                    (x + 10 < vWidth)
                                    && (vTheArray[y][x + 7] == 0)
                                    && (vTheArray[y][x + 8] == 0)
                                    && (vTheArray[y][x + 9] == 0)
                                    && (vTheArray[y][x + 10] == 0)
                                )
                                ||
                                (
                                    (x - 4 >= 0)
                                    && (vTheArray[y][x - 1] == 0)
                                    && (vTheArray[y][x - 2] == 0)
                                    && (vTheArray[y][x - 3] == 0)
                                    && (vTheArray[y][x - 4] == 0)
                                )
                            )
                    )
                {
                    vPenalty += 40;
                }

                if (
                        (y + 6 < vHeight)
                        && (vTheArray[y][x] == 1)
                        && (vTheArray[y + 1][x] == 0)
                        && (vTheArray[y + 2][x] == 1)
                        && (vTheArray[y + 3][x] == 1)
                        && (vTheArray[y + 4][x] == 1)
                        && (vTheArray[y + 5][x] == 0)
                        && (vTheArray[y + 6][x] == 1)
                        && (
                                (
                                    (y + 10 < vHeight)
                                    && (vTheArray[y + 7][x] == 0)
                                    && (vTheArray[y + 8][x] == 0)
                                    && (vTheArray[y + 9][x] == 0)
                                    && (vTheArray[y + 10][x] == 0)
                                )
                                ||
                                (
                                    (y - 4 >= 0)
                                    && (vTheArray[y - 1][x] == 0)
                                    && (vTheArray[y - 2][x] == 0)
                                    && (vTheArray[y - 3][x] == 0)
                                    && (vTheArray[y - 4][x] == 0)
                                )
                            )
                    )
                {
                    vPenalty += 40;
                }
            }
        }
        
        return vPenalty;
    }

    ApplyMaskPenaltyRule4(matrix) {
        let vNumDarkCells = 0;
        let vTheArray = matrix.GetArray();
        let vWidth = matrix.Width;
        let vHeight = matrix.Height;
        for (let y = 0; y < vHeight; y++) {
            for (let x = 0; x < vWidth; x++) {
                if (vTheArray[y][x] == 1)
                    vNumDarkCells++;
            }
        }

        let vNumTotalCells = matrix.Height * matrix.Width;
        let vDarkRatio = vNumDarkCells / vNumTotalCells;
        return Math.round(Math.abs((vDarkRatio * 100 - 50)) / 50);
    }

    GetModeCharacterCountBits(mode, version) {
        let vNumber = version.VersionNumber, vOffset = 0;
        if (vNumber <= 9)
            vOffset = 0;
        else if (vNumber <= 26)
            vOffset = 1;
        else
            vOffset = 2;

        return ModeCharacterCountBits[mode][vOffset];
    }

    Encode(content, encodeOptions, ECLevel, QRCode) {
        let vDataBits = new TBitArray();
        let vHeaderBits = new TBitArray();
        let vInfo = this.ChooseMode(content, encodeOptions);
        encodeOptions = vInfo.encoding;
        let vFilteredContent = this.FilterContent(content, vInfo.mode, encodeOptions);
        this.AppendBytes(vFilteredContent, vInfo.mode, vDataBits, encodeOptions);
        this.AppendModeInfo(vInfo.mode, vHeaderBits);

        let vProvisionalVersion = TVersion.GetVersionForNumber(1);
        let vProvisionalBitsNeeded = vHeaderBits.GetSize()
            + this.GetModeCharacterCountBits(vInfo.mode, vProvisionalVersion)
            + vDataBits.GetSize();

        vProvisionalVersion = TVersion.ChooseVersion(vProvisionalBitsNeeded, ECLevel);
        let vBitsNeeded = vHeaderBits.GetSize()
            + this.GetModeCharacterCountBits(vInfo.mode, vProvisionalVersion)
            + vDataBits.GetSize();

        let vVersion = TVersion.ChooseVersion(vBitsNeeded, ECLevel);
        let vHeaderAndDataBits = new TBitArray();
        let vFinalBits = new TBitArray();
        vHeaderAndDataBits.AppendBitArray(vHeaderBits);

        let vNumLetters = vFilteredContent.length;
        if (vInfo.mode == TQRMode.Byte)
            vNumLetters = vDataBits.GetSizeInBytes();

        this.AppendLengthInfo(vNumLetters, vVersion.VersionNumber, vInfo.mode, vHeaderAndDataBits);
        vHeaderAndDataBits.AppendBitArray(vDataBits);

        let vECBlocks = vVersion.GetECBlocksForLevel(ECLevel);
        let vNumDataBytes = vVersion.GetTotalCodewords() - vECBlocks.GetTotalECCodewords();

        // Terminate the bits properly.
        this.TerminateBits(vNumDataBytes, vHeaderAndDataBits);

        // Interleave data bits with error correction code.
        this.InterleaveWithECBytes(vHeaderAndDataBits, vVersion.GetTotalCodewords(),
            vNumDataBytes, vECBlocks.GetNumBlocks(), vFinalBits);

        QRCode.SetECLevel(ECLevel);
        QRCode.Mode = vInfo.mode;
        QRCode.Version = vVersion.VersionNumber;

        //  Choose the mask pattern and set to "qrCode".
        let vDimension = vVersion.GetDimensionForVersion();
        let vMatrix = new TByteMatrix(vDimension, vDimension);

        QRCode.MaskPattern = this.ChooseMaskPattern(vFinalBits, ECLevel, vVersion.VersionNumber, vMatrix);
        vMatrix = new TByteMatrix(vDimension, vDimension);

        // Build the matrix and set it to "qrCode".
        let vMatrixUtil = new TMatrixUtil();
        vMatrixUtil.BuildMatrix(vFinalBits, QRCode.ECLevel, QRCode.Version, QRCode.MaskPattern, vMatrix);

        QRCode.SetMatrix(vMatrix);  // QRCode will free the matrix
    }
}

class TQRCode extends TObject {
    constructor() {
        super();
        this.Matrix = null;
        this.ECLevel = null;
        this.Mode = TQRMode.Terminator;
        this.Version = -1;
        this.MatrixWidth = -1
        this.MaskPattern = -1;
        this.NumTotalBytes = -1;
        this.NumDataBytes = -1;
        this.NumECBytes = -1;
        this.NumRSBlocks = -1;
        this.QRCodeError = false;
    }

    At(x, y) {
        let vValue = this.Matrix.Get(x, y);
        if (!((vValue == 0) || (vValue == 1)))
            this.QRCodeError = true;

        return vValue;
    }

    IsValid() {
        return ((this.ECLevel != null)
            && (this.Version != -1)
            && (this.MatrixWidth != -1)
            && (this.MaskPattern != -1)
            && (this.NumTotalBytes != -1)
            && (this.NumDataBytes != -1)
            && (this.NumECBytes != -1)
            && (this.NumRSBlocks != -1)
            && this.IsValidMaskPattern(this.MaskPattern)
            && (this.NumTotalBytes == this.NumDataBytes + this.NumECBytes)
            && (this.Matrix != null)
            && (this.MatrixWidth == this.Matrix.Width)
            && (this.Matrix.Width == this.Matrix.Height));
    }

    IsValidMaskPattern(maskPattern) {
        return (maskPattern >= 0) && (maskPattern < NUM_MASK_PATTERNS);
    }

    SetMatrix(newMatrix) {
        this.Matrix = newMatrix;
    }

    SetECLevel(newECLevel) {
        this.ECLevel = new TErrorCorrectionLevel();
        this.ECLevel.Assign(newECLevel);
    }

    SetAll(versionNum, numBytes, numDataBytes, numRSBlocks, numECBytes, matrixWidth) {
        this.Version = versionNum;
        this.NumTotalBytes = numBytes;
        this.NumDataBytes = numDataBytes;
        this.NumRSBlocks = numRSBlocks;
        this.NumECBytes = numECBytes;
        this.MatrixWidth = matrixWidth;
    }
}

class TByteMatrix extends TObject {
    constructor(width, height) {
        super();
        this.Width = width;
        this.Height = height;
        this.Bytes = new TObjectArray(height);
        for (let i = 0; i < height; i++) {
            this.Bytes[i] = new Uint8Array(width);
            for (let j = 0; j < width; j++)
                this.Bytes[i][j] = 0;
        }
    }
  
    Get(x, y) {
        if (this.Bytes[y][x] == 255)
            return -1;
        else
            return this.Bytes[y][x];
    }

    SetBoolean(x, y, val) {
        this.Bytes[y][x] = (val ? 1 : 0) & 0xFF;
    }

    SetInteger(x, y, val) {
        this.Bytes[y][x] = val & 0xFF;
    }

    GetArray() {
        return this.Bytes;
    }

    Assign(source) {
        this.Bytes = source.Bytes.concat();
        this.Width = source.Width;
        this.Height = source.Height;
    }

    Clear(val) {
        for (let y = 0; y < this.Height; y++) {
            for (let x = 0; x < this.Width; x++)
                this.Bytes[y][x] = val;
        }
    }

    Hash() {
        let vResult = "", vCounter, vCC;
        for (let y = 0; y < this.Height; y++) {
            vCounter = 0;
            for (let x = 0; x < this.Width; x++) {
                vCC = this.Get(x, y);
                if (vCC == -1)
                    vCC = 255;
            
                vCounter += vCC;
            }

            vResult += String.fromCharCode((vCounter % 26) + 65);
        }

        return vResult;
    }
}

export class THCQRCode extends TBitmap {
    constructor(text, width, height) {
        super(width, height);
        this.FText = text;
        this.FEncoding = TQRCodeEncoding.Auto;
        this.FElements = null;
        this.FRows = 0;
        this.FColumns = 0;
        this.Encode();
    }

    GenerateQRCode(text, encoding) {
        let vLevel = new TErrorCorrectionLevel();
        vLevel.Bits = 1;

        let vEncoder = new TEncoder();
        let vQRCode = new TQRCode();
        vEncoder.Encode(text, encoding, vLevel, vQRCode);
        return vQRCode.Matrix;
    }

    Encode() {
        if (this.FText == "")
            system.exception(this.className + " 无效的字符！");

        if (this.width < 0 || this.height < 0)
            system.exception(this.className + "无效的尺寸！");

        this.FElements = this.GenerateQRCode(this.FText, this.FEncoding);
        this.FRows = this.FElements.Height;
        this.FColumns = this.FElements.Width;

        // 绘制
        //this.canvas.clear(0, 0, this.FColumns, this.FRows);

        this.canvas.brush.color = TColor.Red;
        this.canvas.fillBounds(0, 0, this.width, this.height);

        this.canvas.pen.color = TColor.Black;
        let vBytes;
        this.canvas.beginPath();
        try {
            for (let y = 0; y < this.FRows; y++) {
                vBytes = this.FElements.Bytes[y];
                for (let x = 0; x < this.FColumns; x++) {
                    if (vBytes[x] == 1) {
                        this.canvas.moveTo(x, y);
                        this.canvas.lineTo(x + 1, y);
                    }
                }
            }
        } finally {
            this.canvas.paintPath();
        }
    }

    get Text() {
        return this.FText;
    }

    set Text(val) {
        if (this.FText != val) {
            this.FText = val;
            this.Encode();
        }
    }
}

export class THCQRCodeItem extends THCResizeRectItem {
    constructor(ownerData, text) {
        super(ownerData);
        this.StyleNo = THCStyle.QRCode;
        this.Width = 100;
        this.Height = 100;
        this.FQRCode = new THCQRCode(text, 100, 100);
    }

    DoPaint(style, drawRect, dataDrawTop, dataDrawBottom, dataScreenTop, dataScreenBottom,
        hclCanvas, paintInfo)
    {
        hclCanvas.bitBlt(drawRect.left, drawRect.top, drawRect.width, drawRect.height,
            this.FQRCode.canvas, 0, 0, this.FQRCode.width, this.FQRCode.height);
    }

    GetText() {
        return this.FQRCode.Text;
    }

    SetText(val) {
        this.FQRCode.Text = val;
    }

    SaveToStreamRange(stream, start, end) {
        super.SaveToStreamRange(stream, start, end);
        HC.HCSaveTextToStream(stream, this.FQRCode.Text);
    }

    LoadFromStream(stream, style, fileVersion) {
        super.LoadFromStream(stream, style, fileVersion);
        this.FQRCode.Text = HC.HCLoadTextFromStream(stream, fileVersion);
    }

    ToHtml() {
        return "";
    }

    ToXml(node) {
        super.ToXml(node);
        node.InnerText = this.FQRCode.Text;
    }

    ParseXml(node) { }

    RestrainSize(width, height) {
        let vBL;
        if (this.Width > width) {
            vBL = this.Width / width;
            this.Width = width;
            this.Height = Math.round(this.Height / vBL);
        }

        if (this.Height > height) {
            vBL = this.Height / height;
            this.Height = height;
            this.Width = Math.round(this.Width / vBL);
        }
    }
}